<?php
/**
 *
 * @author ninteySeven.
 * @date 2023-03-26 11:41
 */

namespace App\Services;


use App\Enum\NormalEnum;
use App\Models\DispatchDateModel;
use App\Models\OrderModel;
use App\Models\TransportModel;
use Illuminate\Support\Facades\Log;

class DispatchService extends BaseService
{

    public static function dispatchTransport()
    {
        try {
            // 找到轮循日期和城市
            $pollingDates = self::_getDispatchDate();
            if (!empty($pollingDates)) {
                foreach( $pollingDates as $pollingRow ){
                    $polling = $pollingRow->toArray();

                    Log::notice(sprintf("自动分配订单给承运商：日期（%s）和城市（%s）", $polling['work_date'], $polling['city_name']));

                    // 找到轮循日期和城市下的 承运商(排序)
                    $transports = self::_getTransports($polling['city_code']);

                    if(empty($transports)) {
                        Log::notice(sprintf("该城市（%s）无承运商。next-",  $polling['city_name']));
                        continue;
                    }

                    // 如果只有1个承运商，将城市下的订单都分配给它
                    if(sizeof($transports) == 1) {
                        $numberDispatched = self::_dispatchAllToTransport($polling, $transports[0]);
                    }else{
                        // 按照算法分配承运商订单
                        $numberDispatched = self::_dispatchTransportWithAlgorithm($polling,$transports);
                    }

                    Log::notice(sprintf("城市（%s）分配订单数:%d", $polling['city_name'], $numberDispatched));

                    // 增加处理数量
                    $pollingRow->increment('dispatch_num', $numberDispatched);

                    // 小憩一下
                    sleep(1);
                }
            }else{
                Log::notice("自动分配订单给承运商：日期和城市都没有");
            }
        }catch (\Exception $e){
            throw new \Exception("分配承运商失败:".$e->getMessage());
        }
    }
    /**
     * 获取要工作的日期(大于今天)
     *
     * @return \Illuminate\Database\Eloquent\Builder[]|\Illuminate\Database\Eloquent\Collection
     */
    private static function _getDispatchDate()
    {
         return DispatchDateModel::query()->where(['status'=>NormalEnum::STATUS_NORMAL])
             ->where('work_date','>=', date('Y-m-d'))
             ->orderBy('work_date')->get(['id', 'city_code', 'work_date', 'city_name']);
    }
    /**
     * 找到城市下的承运商。排序（系数大的在前，ID小的在前）
     *
     * @param $cityCode
     * @return array
     */
    private static function _getTransports($cityCode): array
    {
        return TransportModel::query()->where(['city_code'=>$cityCode])->orderByDesc('dispatch_coefficient')->orderBy('id')->get(['id','dispatch_coefficient','phone','transport_name'])->toArray();
    }
    /**
     * 将某日的城市订单分配给唯一的承运商
     *
     * @param $polling
     * @param $transport
     * @return int
     */
    private static function _dispatchAllToTransport($polling, $transport): int
    {
        $beginTime = strtotime($polling['work_date']);
        $endTime = $beginTime + 86400;
        $where = [
            'transport_id'=>0,
            'city_code' => $polling['city_code']
        ];
        $orderIds = OrderModel::query()->whereBetween('start_time',[$beginTime,$endTime])->where($where)->get(['id']);
        if( !empty($orderIds) ) return 0;
        $orderIds = array_column($orderIds->toArray(), 'id');
        // 分配承运商
        $result = OrderModel::query()->whereIn('id',$orderIds)->update(['transport_id'=>$transport['id']]);
        if( !empty($result) ) {
            //  标记订单为待回馈
            self::_feedOrderChanged($orderIds);
            return $result;
        }
        return 0;

    }

    /**
     * 将轮循日期和城市的订单进行轮循分配
     *
     * @param $polling
     * @param $transports
     * @return int 分配的数量
     */
    private static function _dispatchTransportWithAlgorithm($polling,$transports): int
    {
        // 通过已经分配的订单找到应该分配的承运商
        $transport = self::_getNextTransport($polling,$transports);
        $numberDispatch = 0;
        $complementNum = $transport['complement_num'] ?? 0;
        while( true )
        {
            $numberDispatchEach = self::_dispatchOrdersToTransport($polling,$transport,$complementNum);

            // 统计处理数字
            $numberDispatch += $numberDispatchEach;

            // 如果首次要补足
            if( $complementNum > 0 ){
                // 如果还没补足，跳出
                if( $numberDispatchEach !== $complementNum )
                    break;
                $complementNum = 0;
                $transport = self::_calculateNextTransport($transport,$transports);
            }elseif( $numberDispatchEach == $transport['dispatch_coefficient'] ) {
                // 如果足额分配,则找到下一个承运商；否则，没单要分配（退出循环）
                $transport = self::_calculateNextTransport($transport,$transports);
            } else break;

            // 小憩一下
            sleep(1);
        }
        return $numberDispatch;
    }

    /**
     * 找到下一个要派单的承运商
     *
     * @param $polling
     * @param $transports
     * @return array
     */
    private static function _getNextTransport($polling,$transports): array
    {
        $maxDispatchCoefficient = env('MAX_DISPATCH_COEFFICIENT', 5) + 1;

        // 取得最近的几个订单
        $latestOrders = self::_getLatestDispatchOrders($polling, $maxDispatchCoefficient);
        $latestDispatchTransport = self::_formatDispatchedOrders($latestOrders);

        return self::_calculateNextTransport($latestDispatchTransport, $transports);

    }
    private static function _getLatestDispatchOrders(array $polling, int $maxDispatchCoefficient)
    {
        $beginTime = strtotime($polling['work_date']);
        $endTime = $beginTime + 86400;
        $where = [
            'city_code' => $polling['city_code']
        ];
        return OrderModel::query()->whereBetween('start_time',[$beginTime,$endTime])
            ->where($where)->where('transport_id','>',0)
            ->orderByDesc('start_time')->orderBy('id')
            ->limit($maxDispatchCoefficient)
            ->get(['id','transport_id']);
    }

    /**
     * 格式化订单，取得最近给哪个承运商分配了多少单
     *
     * @param $orders
     * @return array
     */
    private static function _formatDispatchedOrders($orders): array
    {
        if( empty($orders) ) return [];
        $transportId = 0;
        $num = 0;
        foreach ($orders as $order){
            if( $transportId === 0 ) $transportId = $order['transport_id'];
            if( $transportId != $order['transport_id'] ) break;
            $num++;
        }
        return ['id' => $transportId, 'dispatch_coefficient'=>$num];
    }

    /**
     * 传入当前分配的承运商，返回下一个待分配的承运商
     *
     * @param $latestDispatchTransport
     * @param $transports
     * @return array
     */
    private static function _calculateNextTransport($latestDispatchTransport, $transports): array
    {
        $found = false;
        $result = null;
        foreach( $transports as $transport )
        {
            // 如果最近分配的已经满了，则返回下一个承运商
            if( $found ) {
                $result = $transport;
                break;
            }

            if($transport['id'] == $latestDispatchTransport['id'])
            {
                $found = true;
                $remainNum  = $transport['dispatch_coefficient'] - $latestDispatchTransport['dispatch_coefficient'];
                // 未分配满，则补足
                if( $remainNum >0 ){
                    $result = $transport;
                    $result['complement_num'] = $remainNum;
                    break;
                }
            }
        }
        // 如果找到要分配的对象，直接分配。若未找到，说明要从头开始
        if( empty($result) ) $result = $transports[0];

        return $result;
    }

    /**
     * 给承运商分配订单
     *
     * @param $polling
     * @param $transport
     * @param int $complementNum
     * @return int 分配的订单数
     */
    private static function _dispatchOrdersToTransport($polling, $transport, int $complementNum = 0): int
    {
        $beginTime = strtotime($polling['work_date']);
        $endTime = $beginTime + 86400;
        $where = [
            'transport_id'=>0,
            'city_code' => $polling['city_code']
        ];

        // 补足优先
        if( $complementNum > 0)
            $num = $complementNum;
        else
            $num = $transport['dispatch_coefficient'];

        // 找到合适的订单
        $orders = OrderModel::query()->whereBetween('start_time',[$beginTime,$endTime])->where($where)
                    ->orderBy('start_time')->orderBy('id')
            ->limit($num)->get(['id'])->toArray();

        $sizeofOrders = sizeof($orders);

        // 将找到的订单分配给承运商
        if( !empty($orders) )
        {
            $orderIds = array_column($orders,'id');

            // 找到回程的订单ID，尽量保证数量不超sizeof(orders)
            $orderIds = self::_findBackOrder($orderIds);

            Log::notice(sprintf("分配给（%s），订单ID（%s）",$transport['transport_name'],implode(',', $orderIds)));
            // 分配承运商
            $result = OrderModel::query()->whereIn('id',$orderIds)->update(['transport_id'=>$transport['id']]);
            if( !empty($result) ) {

                //  标记订单为待回馈
                self::_feedOrderChanged($orderIds);

                return $result;
            }
            return 0;
        }else{
            Log::notice("获取要分配的订单ID[空]");
        }

        return $sizeofOrders;
    }

    /**
     * 确保往返订单在一起
     *
     * @param $orderIds
     * @return array
     */
    private static function _findBackOrder($orderIds): array
    {
        $rows = OrderModel::query()->whereIn('go_order_id',$orderIds)->get(['id'])->toArray();
        if(!empty($rows))
        {
            $backOrderIds = array_column($rows,'id');
            foreach( array_diff($backOrderIds,$orderIds) as $orderId )$orderIds[]= $orderId;
        }
        return $orderIds;
    }

    /**
     * 将订单id加入 信息回馈
     * @param array $orderIds
     * @return void
     */
    private static function _feedOrderChanged(array $orderIds)
    {
        // 如果 只分配承运商 也要回馈订单信息
        if( env('DISPATCH_TRANSPORT_FEEDBACK', false) )
        {
            OrderService::addOrderChanged($orderIds);
        }
    }
}
